package com.zxq.kingofbots.consumer.utils;

import com.alibaba.fastjson2.JSONObject;
import com.zxq.kingofbots.consumer.WebSocketServer;
import com.zxq.kingofbots.pojo.Bot;
import com.zxq.kingofbots.pojo.Record;
import com.zxq.kingofbots.pojo.User;
import lombok.Getter;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

public class Game extends Thread {
    private Integer rows;
    private Integer cols;
    private Integer innerWallsCount;
    @Getter
    private int[][] g;
    private Integer nextStepA = null;
    private Integer nextStepB = null;
    private static int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};

    private ReentrantLock lock = new ReentrantLock();
    private String status = "playing";//playing进行  over结束
    private String loser = "none";
    @Getter
    private Player playerA, playerB;

    private final static String addBotUrl="http://127.0.0.1:3002/bot/add";

    public Game(Integer rows, Integer cols, Integer innerWallsCount, Integer idA, Bot aBot , Integer idB, Bot bBot) {
        this.rows = rows;
        this.cols = cols;
        this.innerWallsCount = innerWallsCount;
        this.g = new int[rows][cols];

        Integer aBotId=-1,bBotId=-1;
        String aBotCode="",bBotCode="";

        if(aBot!=null){
            aBotId=aBot.getId();
            aBotCode=aBot.getContent();
        }

        if(bBot!=null){
            bBotId=bBot.getId();
            bBotCode=bBot.getContent();
        }

        this.playerA = new Player(idA, aBotId,aBotCode,this.rows - 2, 1, new ArrayList<>());
        this.playerB = new Player(idB, bBotId,bBotCode,1, this.cols - 2, new ArrayList<>());
    }

    public void setNextStepA(Integer nextStepA) {
        lock.lock();
        try {
            this.nextStepA = nextStepA;
        } finally {
            lock.unlock();
        }
    }

    public void setNextStepB(Integer nextStepB) {
        lock.lock();
        try {
            this.nextStepB = nextStepB;
        } finally {
            lock.unlock();
        }
    }

    private boolean draw() {  // 画地图
        for (int i = 0; i < this.rows; i++) {
            for (int j = 0; j < this.cols; j++) {
                g[i][j] = 0;
            }
        }

        for (int r = 0; r < this.rows; r++) {
            g[r][0] = g[r][this.cols - 1] = 1;
        }
        for (int c = 0; c < this.cols; c++) {
            g[0][c] = g[this.rows - 1][c] = 1;
        }

        Random random = new Random();
        for (int i = 0; i < this.innerWallsCount / 2; i++) {
            for (int j = 0; j < 1000; j++) {
                int r = random.nextInt(this.rows);
                int c = random.nextInt(this.cols);

                if (g[r][c] == 1 || g[this.rows - 1 - r][this.cols - 1 - c] == 1)
                    continue;
                if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2)
                    continue;

                g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = 1;
                break;
            }
        }
        return check_connectivity(this.rows - 2, 1, 1, this.cols - 2);
    }

    private boolean check_connectivity(int sx, int sy, int tx, int ty) {
        if (sx == tx && sy == ty) return true;
        g[sx][sy] = 1;

        for (int i = 0; i < 4; i++) {
            int x = sx + dx[i], y = sy + dy[i];
            if (x >= 0 && x < this.rows && y >= 0 && y < this.cols && g[x][y] == 0) {
                if (check_connectivity(x, y, tx, ty)) {
                    g[sx][sy] = 0;
                    return true;
                }
            }
        }

        g[sx][sy] = 0;
        return false;
    }

    public void createMap() {
        for (int i = 0; i < 1000; i++) {
            if (draw())
                break;
        }
    }

    private String getInput(Player player){//将当前的局面信息编码成字符串
        Player me,you;
        if(player.getId().equals(playerA.getId())){
            me=playerA;
            you=playerB;
        }else{
            me=playerB;
            you=playerA;
        }

        return getMapString()+"#"+
                me.getSx()+"#"+
                me.getSy()+"#"+"("+
                me.getStepsString()+")#"+
                you.getSx()+"#"+
                you.getSy()+"#"+"("+
                you.getStepsString()+")";
    }

    private void sendBotCode(Player player){
        if(player.getBotId().equals(-1)) return;
        MultiValueMap<String,String> data=new LinkedMultiValueMap<>();
        data.add("user_id",player.getId().toString());
        data.add("bot_code",player.getBotCode());
        data.add("input",getInput(player));
        WebSocketServer.restTemplate.postForObject(addBotUrl,data,String.class);
    }

    private boolean nextStep() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        sendBotCode(playerA);
        sendBotCode(playerB);

        for (int i = 0; i < 50; i++) {
            try {
                Thread.sleep(100);
                lock.lock();
                try {
                    if (nextStepA != null && nextStepB != null) {
                        playerA.getSteps().add(nextStepA);
                        playerB.getSteps().add(nextStepB);
                        return true;
                    }
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }


    private boolean check_valid(List<Cell> cellsA, List<Cell> cellsB) {
        int n=cellsA.size();
        Cell cell=cellsA.get(n-1);
        if(g[cell.row][cell.col]==1){
            return false;
        }
        for(int i=0;i<n-1;i++){
            if(cellsA.get(i).row==cell.row&&cellsA.get(i).col==cell.col){
                return false;
            }
        }
        for(int i=0;i<n-1;i++){
            if(cellsB.get(i).row==cell.row&&cellsB.get(i).col==cell.col){
                return false;
            }
        }
        return true;
    }


    //判断两名玩家下一步是否合法
    public void judge() {
        List<Cell> cellsA = playerA.getCells();
        List<Cell> cellsB = playerB.getCells();
        System.out.println(cellsA.toString());

        boolean validA = check_valid(cellsA, cellsB);
        boolean validB = check_valid(cellsB, cellsA);
        if(!validA||!validB){
            status="over";
            if(!validA&&!validB){
                loser="all";
            }else if(!validA){
                loser="A";
            }else {
                loser="B";
            }
        }
    }


    public void sendMove() {
        lock.lock();
        try {
            JSONObject resp = new JSONObject();
            resp.put("event", "move");
            resp.put("a_direction", nextStepA);
            resp.put("b_direction", nextStepB);
            nextStepA = nextStepB = null;
            sendAllMessage(resp.toJSONString());
        } finally {
            lock.unlock();
        }
    }

    private String getMapString(){
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < rows; i ++ ) {
            for (int j = 0; j < cols; j ++ ) {
                res.append(g[i][j]);
            }
        }
        return res.toString();
    }

    private void UpdateRating(Player player,Integer rating){
        User user=WebSocketServer.userMapper.selectById(player.getId());
        user.setRating(rating);
        WebSocketServer.userMapper.updateById(user);
    }

    private void saveToDatabase(){
        Integer ratingA=WebSocketServer.userMapper.selectById(playerA.getId()).getRating();
        Integer ratingB=WebSocketServer.userMapper.selectById(playerB.getId()).getRating();

        Record record=new Record(
                null,
                playerA.getId(),
                playerA.getSx(),
                playerA.getSy(),
                playerB.getId(),
                playerB.getSx(),
                playerB.getSy(),
                playerA.getStepsString(),
                playerB.getStepsString(),
                getMapString(),
                loser,
                new Date()
        );

        if("A".equals(loser)){
            ratingA-=2;
            ratingB+=2;
        }else if("B".equals(loser)){
            ratingA+=2;
            ratingB-=2;
        }

        UpdateRating(playerA,ratingA);
        UpdateRating(playerB,ratingB);

        WebSocketServer.recordMapper.insert(record);
    }

    public void sendResult() {
        JSONObject resp = new JSONObject();
        resp.put("event", "result");
        resp.put("loser", loser);
        saveToDatabase();
        sendAllMessage(resp.toJSONString());
    }

    public void sendAllMessage(String message) {
        if(WebSocketServer.users.get(playerA.getId())!=null)
            WebSocketServer.users.get(playerA.getId()).SendMessage(message);
        if(WebSocketServer.users.get(playerB.getId())!=null)
            WebSocketServer.users.get(playerB.getId()).SendMessage(message);
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if (nextStep()) {
                judge();
                if ("playing".equals(status)) {
                    sendMove();
                } else {
                    sendMove();
                    sendResult();
                    break;
                }
            } else {
                status = "over";
                lock.lock();
                try {
                    if (nextStepA == null && nextStepB == null) {
                        loser = "all";
                    } else if (nextStepA == null) {
                        loser = "A";
                    } else {
                        loser = "B";
                    }
                } finally {
                    lock.unlock();
                }
                sendResult();
                break;
            }
        }
    }
}